/*
Copyright (c) 2025 HeZongLun
NahidaProject is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
		 http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

#include "CommandLineParser.h"

void NahidaProject::CommandLineParser::Add(std::vector<std::string> const& opts, NahidaProject::CommandLineParser::Value const& value, std::string const& desc) {
	args_.emplace_back(Argument{ opts, value, desc });
}

void NahidaProject::CommandLineParser::PrintHelp(std::ostream& os) const {
	const auto maxOptionLength = MaxOptionsLength();

	for (auto const& arg : args_) {
		std::string opts;

		for (auto const& opt : arg.options_) {
			opts += opt + ", ";
		}

		std::stringstream sstr;
		sstr << std::left << std::setw(maxOptionLength) << opts.substr(0, opts.size() - 2);

		size_t spacePos = 0;
		size_t lineWidth = 0;

		while (spacePos != std::string::npos) {
			size_t nextspacePos = arg.desc_.find_first_of(' ', spacePos + 1);
			sstr << arg.desc_.substr(spacePos, nextspacePos - spacePos);
			lineWidth += nextspacePos - spacePos;
			spacePos = nextspacePos;

			if (lineWidth > 60) {
				os << sstr.str() << std::endl;
				sstr = std::stringstream();
				sstr << std::left << std::setw(maxOptionLength - 1) << " ";
				lineWidth = 0;
			}
		}
	}
}

void NahidaProject::CommandLineParser::Parse(int argc, char* argv[])const {
	int i = 1;

	while (i < argc) {
		std::string flag(argv[i]);
		std::string value;
		bool valueIsSeparate = false;
		size_t equalPos = flag.find('=');

		if (equalPos != std::string::npos) {
			value = flag.substr(equalPos + 1);
			flag = flag.substr(0, equalPos);
		}
		else if (i + 1 < argc) {
			value = argv[i + 1];
			valueIsSeparate = true;
		}

		bool foundArgument = false;

		for (auto const& argument : args_) {
			if (std::find(argument.options_.begin(), argument.options_.end(), flag) != std::end(argument.options_)) {
				foundArgument = true;
				if (std::holds_alternative<bool*>(argument.value_)) {
					if (!value.empty() && value != "true" && value != "false") {
						valueIsSeparate = false;
					}

					*std::get<bool*>(argument.value_) = (value != "false");
				}
				else if (value.empty()) {
					throw std::runtime_error("Failed to parse command line arguments: Missing value for argument \"" + flag + "\"!");
				}
				else if (std::holds_alternative<std::string*>(argument.value_)) {
					*std::get<std::string*>(argument.value_) = value;
				}
				else {
					std::visit([&value](auto&& arg) {
						std::stringstream sstr(value);
						sstr >> *arg;
						}, argument.value_);
				}
				break;
			}
		}

		if (!foundArgument) {
			std::cerr << "Unknown argument: \"" << flag << "\"." << std::endl;
		}

		++i;
		if (foundArgument && valueIsSeparate) {
			++i;
		}
	}
}